package com.skyk.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.skyk.config.exception.BaseException;
import com.skyk.mapper.UserMapper;
import com.skyk.pojo.dto.UserAddDTO;
import com.skyk.pojo.dto.UserDTO;
import com.skyk.pojo.dto.UserLoginDTO;
import com.skyk.pojo.entity.User;
import com.skyk.pojo.vo.UserExportVO;
import com.skyk.service.UserService;
import com.skyk.util.Result;
import com.skyk.util.upload.UserListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author 86147
 * @description 针对表【user】的数据库操作Service实现
 * @createDate 2024-11-27 17:31:14
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    //使用baseMapper来简化代码，不用再手动注入UserMapper，可以看ServiceImpl里面的源码看看为什么

    //创建(sessionKey和用户信息)的对象
    //使用类名调用sessionManger对象,这两种sessionManger指向同一个地方
    /* UserSessionManger sessionManger = SessionKeyInterceptor.sessionManger;*/


    private final RedisTemplate<String, Object> redisTemplate;

    public UserServiceImpl(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }


    public String loginService(UserLoginDTO userLoginDTO) {
        //根据前端请求提供的用户名和密码查找用户
        String username = userLoginDTO.getUsername();
        String password = userLoginDTO.getPassword();

        //前端请求的用户名或密码为空时，返回null
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return null;
        }

        //私钥解密
        //私钥
        String privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAJbQY9LjF7HpfeFvfhodEQdLhOlaWvVt1h5rsNvqTlTqAfTph6RQaCy+u0G7vG1XPqcROF+nZGg+RNSgR0BA1ELpExT2GNGr/3pl9zL4szzzgin1b9g0Eccbf+1Pse/ywOvdcK8PdHko0dHVWO7Neqlod9RXFklEQxkHfQcoGFI9AgMBAAECgYARCljNIkWyH4zOpRddgf1Dkr/g0gnHA3MXGtnv70iMhahU8PIXmdI7hA2KftjqjdoRbHuvKy3ILYj/cWC7nNeez2YHwFxdl7cM7QTrRvAodtDnTG1TMg/gdK6+yeP5ohQVoySJFy9bxMmntE8v8lke+kwiPY0vN6T2LVUWyQfWzQJBAMmb0cCi3JK23rnMuBQvaf2OlBpVGE/I8PYzuULJNeV1tYmmNeg+pg1f5OHSRACiUxSZusbTO4ddEyM99GjTbcMCQQC/gGsxuRirdpDQkybJJ2+RwFjKqV505Lwg+CT0lEBKSYcue4n/v7scFlRSDCwlsNh5TetRbmn62A/56dEF6T//AkBfW5oseVvG8UQLsa7nrT1+tq+KfPFQ24RNj01zL2SUscY54yngPbGg5diGj0Om4SBWwBe97y9a6UUiCC3QXyCtAkB+dEJUlzFfJ/AAiur1sYDO8gCv8p9Y+SHxc2qTxsGdTGhjnVhG9UiNB4GunDMawGhx0a4uG4cICVyjXm6URK3pAkB7vqQEYgFUnWgsbWk9JYIiI6mzbQgPpYTPnA2hGcWx8RT5GdHFyDhrnk15BwR/2ShBRCRNhWkAVq0q0xWfoHP2";
        RSA rsa = new RSA(privateKey, null);
        String decryptStr = rsa.decryptStr(password, KeyType.PrivateKey);
        System.out.println(decryptStr);

        //使用LambdaQueryWrapper构建查询条件
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, username);
        //调用 ServiceImpl 类中的 getOne 方法，传入之前构建的 queryWrapper 作为参数
        //getOne 方法根据提供的查询条件执行查询，返回查询结果中的第一个用户对象
        User user = this.getOne(queryWrapper);


        //用户不为null且未被禁用
        if (user != null && user.getState()) {

            //查询数据库中用户的密码并使用私钥解密
            String passwordDB = rsa.decryptStr(user.getPassword(), KeyType.PrivateKey);

            //对比 前端解密后的密码 与 数据库中解密的密码
            boolean flag = passwordDB.equals(decryptStr);

            //密码相等才可以登录
            if (flag) {
                /*这里将随机字符串作为sessionKey,用户信息作为value*/
                String sessionKey = UUID.randomUUID().toString();

                //将sessionKey和用户信息存储在Redis中
                redisTemplate.opsForValue().set(sessionKey, user, 30, TimeUnit.MINUTES);//设置30分钟的过期时间
                return sessionKey;
            } else {
                return null;
            }

        } else {
            return null;
        }
    }


    /*
     *分页查询
     * */
    public IPage<UserDTO> getUsersPage(Page<User> page, String username, String gender, String phoneNumber) {

        //使用LambdaQueryWrapper构建查询条件
        //判断不是空白isNotBlank。空白的定义是null,"",不可见字符
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .like(StrUtil.isNotBlank(username), User::getUsername, username)
                .eq(StrUtil.isNotBlank(gender), User::getGender, gender)
                .eq(StrUtil.isNotBlank(phoneNumber), User::getPhoneNumber, phoneNumber);
        // 也可以这样简化
        IPage<User> userPage2 = lambdaQuery()
                .like(StrUtil.isNotBlank(username), User::getUsername, username)
                .eq(StrUtil.isNotBlank(gender), User::getGender, gender)
                .eq(StrUtil.isNotBlank(phoneNumber), User::getPhoneNumber, phoneNumber)
                .page(page);

        // 使用MyBatis Plus的selectPage方法进行分页查询
        //接收分页信息和查询条件作为参数
        //返回的对象包含了分页信息和查询结果（User实体列表）
        IPage<User> userPage = baseMapper.selectPage(page, queryWrapper);

        //创建一个新的Page<UserDTO>对象来存储转换后的DTO分页信息
        Page<UserDTO> userDTOPage = new Page<>(page.getCurrent(), page.getSize());
        //创建一个空的List<UserDTO>来存储转换后的DTO对象
        List<UserDTO> userDTOList = new ArrayList<>();

        //Convert万能转换器
        //通过convert(TypeReference<T> reference, Object value)方法，自行new一个TypeReference对象可以对嵌套泛型进行类型转换
        //userPage.getRecords() ===> List<User> 转换成  List<UserDTO>
        List<UserDTO> userDTOList2 = Convert.convert(new TypeReference<List<UserDTO>>() {
        }, userPage.getRecords());

        /*遍历查询结果（User实体列表），对于每个User实体，创建一个新的UserDTO对象，
        并使用BeanUtils.copyProperties方法将User实体的属性复制到UserDTO对象中。*/
        for (User user : userPage.getRecords()) {
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(user, userDTO);
            userDTOList.add(userDTO);
        }

        //将转换后的DTO列表设置为userDTOPage的 记录
        userDTOPage.setRecords(userDTOList);
        //将原始查询结果的总记录数设置为userDTOPage的 总记录数
        userDTOPage.setTotal(userPage.getTotal());

        //返回userDTOPage对象，它现在包含了分页信息和转换后的查询结果（UserDTO对象列表）
        return userDTOPage;
    }


    /*
     * 逻辑删除
     * */
    //  涉及新增修改的方法，记得加上@Transactional注解，并且指定需要回滚的异常类型，默认写死Exception.class
    /*由于方法被 @Transactional 注解，如果 removeById(id) 方法（或方法内部调用的任何其他数据库操作）失败并抛出了 Exception 或其子类的异常，
    那么整个事务将回滚。这意味着，如果逻辑删除失败，那么在该事务中执行的所有其他数据库操作也将被撤销。*/
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void logicDeleteUserById(Long id) {
        //MaBatis Plus 会自动执行逻辑删除操作
        //deleteById方法返回 受影响的行数 。通过比较返回值是否大于0来判断删除操作是否成功
        //  属于当前类的mapper接口，可以直接使用这个代替，也可以直接下面的removeById
        //baseMapper.deleteById(id);
        removeById(id);

    }

    /*
     * 启用或禁用用户
     * */
    @Transactional(rollbackFor = Exception.class)
    public void updateOrStop(Boolean state, Long id) {
        //  可以这样写
        // 1
        User user = new User();
        user.setId(id);
        user.setState(state);
        updateById(user);
        // 2
       /* update(new LambdaUpdateWrapper<User>()
                .set(User::getState, state)
                .eq(User::getId, id));*/
        // 3
       /* update(lambdaUpdate()
                .set(User::getState, state)
                .eq(User::getId, id));*/
    }


    /*新增用户*/
    @Transactional(rollbackFor = Exception.class)
    public Result addUser(UserAddDTO userAddDTO) throws RuntimeException {
        //创建一个user对象
        User user = new User();

        //将userAddDTO的内容放到user里面
        BeanUtils.copyProperties(userAddDTO, user);

        // 可以简化下，用lambda直接引用实体类的属性名，不要手写字段，查找存不存在，可以用count()，不用直接插数据出来
        Integer count = lambdaQuery().eq(User::getPhoneNumber, user.getPhoneNumber()).count();
        if (count > 0) {
            return Result.error("手机号已经存在");
        } else {
            //使用mybatis-plus的save方法保存用户
            this.save(user);
            return Result.success();
        }

        /*//校验手机号是否已经存在(不校验已经被删除的用户)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("phone_number", user.getPhoneNumber())
                .ne("deleteIs", 1);//deleteIs != 1

        User existingUser = this.getOne(queryWrapper);

        if (existingUser != null) {
            //手机号已经存在
            return Result.error("手机号已经存在");
        }else{
            //使用mybatis-plus的save方法保存用户
            this.save(user);
            return Result.success();
        }*/
    }


    /*更新用户*/
    @Transactional(rollbackFor = Exception.class)
    //  没东西返回就填个Void，空着不好
    public Result<Void> updateUser(Long id, UserAddDTO userAddDTO) throws RuntimeException {
        User user = new User();
        user.setId(id);
        BeanUtils.copyProperties(userAddDTO, user);

        //  不要手写字段，还有定义了TableLogic的，不用手动写上去

        //校验手机号是否已经存在(不校验已经被删除的用户,不校验自己)
        Integer count = lambdaQuery().eq(User::getPhoneNumber, user.getPhoneNumber())
                .ne(User::getId, user.getId()).count();
        if (count > 0) {
            //手机号已经存在
            throw new BaseException("手机号已存在");
        } else {
            //使用mybatis-plus的save方法保存用户
            updateById(user);
            return Result.success();
        }

       /* //校验手机号是否已经存在(不校验已经被删除的用户,不校验自己)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("phone_number", user.getPhoneNumber())
                .ne("id", user.getId());

        User existingUser = this.getOne(queryWrapper);

        if (existingUser != null) {
            //手机号已经存在
            //  有错误直接抛异常，有全局异常拦截帮忙处理
//            throw new BaseException("手机号已存在");
            return Result.error("手机号已存在");
        } else {
            //使用mybatis-plus的save方法保存用户
            this.updateById(user);
            return Result.success();
        }*/
    }

    /*导出用户*/
    public void exportUsers(HttpServletResponse response, String username, String gender, String phoneNumber) throws IOException {

        //根据搜索条件查询用户
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .like(StrUtil.isNotBlank(username), User::getUsername, username)
                .eq(StrUtil.isNotBlank(gender), User::getGender, gender)
                .eq(StrUtil.isNotBlank(phoneNumber), User::getPhoneNumber, phoneNumber);
        List<User> userList = baseMapper.selectList(queryWrapper);


        //设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("users", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        //使用EasyExcel导出数据
        EasyExcel.write(response.getOutputStream(), User.class)
                .sheet("用户数据")
                .doWrite(userList);

    }

    @Override
    public void importUsers(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), UserExportVO.class, new UserListener(this)).sheet().doRead();
    }

    @Override
    public void exportUsersV2(HttpServletResponse response, String username, String gender, String phoneNumber) throws IOException {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
                .like(StrUtil.isNotBlank(username), User::getUsername, username)
                .eq(StrUtil.isNotBlank(gender), User::getGender, gender)
                .eq(StrUtil.isNotBlank(phoneNumber), User::getPhoneNumber, phoneNumber);
        List<User> userList = baseMapper.selectList(queryWrapper);

        //设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode("users", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        List<UserExportVO> userVoList = new LinkedList<>();
        for (User user : userList) {
            UserExportVO userExportVO = new UserExportVO();
            BeanUtils.copyProperties(user, userExportVO);
            userExportVO.setState(user.getState() ? "启用" : "禁用");
            userVoList.add(userExportVO);
        }

        //使用EasyExcel导出数据
        EasyExcel.write(response.getOutputStream(), UserExportVO.class)
                .sheet("用户数据")
                .doWrite(userVoList);
    }

}









